import Foundation
import ExpoModulesCore

// MARK: - Swift Implementation for Iroh Integration

public class IrohModule: Module {
    public func definition() -> ModuleDefinition {
        Name("Iroh")
        
        // MARK: - Node Management
        AsyncFunction("createNode") { (config: [String: Any]?) -> [String: Any] in
            return try await createIrohNode(config: config)
        }
        
        AsyncFunction("getNodeInfo") { () -> [String: Any] in
            return try await getNodeInfo()
        }
        
        AsyncFunction("shutdownNode") { () -> Bool in
            return try await shutdownNode()
        }
        
        // MARK: - Document Operations
        AsyncFunction("createDocument") { (name: String) -> [String: Any] in
            return try await createDocument(name: name)
        }
        
        AsyncFunction("getDocument") { (documentId: String) -> [String: Any]? in
            return try await getDocument(documentId: documentId)
        }
        
        AsyncFunction("listDocuments") { () -> [[String: Any]] in
            return try await listDocuments()
        }
        
        AsyncFunction("deleteDocument") { (documentId: String) -> Bool in
            return try await deleteDocument(documentId: documentId)
        }
        
        // MARK: - Blob Operations
        AsyncFunction("addBlob") { (data: Data) -> [String: Any] in
            return try await addBlob(data: data)
        }
        
        AsyncFunction("getBlob") { (hash: String) -> Data? in
            return try await getBlob(hash: hash)
        }
        
        // MARK: - File Operations
        AsyncFunction("addFile") { (filePath: String) -> [String: Any] in
            return try await addFile(filePath: filePath)
        }
        
        AsyncFunction("exportFile") { (hash: String, outputPath: String) -> Bool in
            return try await exportFile(hash: hash, outputPath: outputPath)
        }
        
        // MARK: - Network Operations
        AsyncFunction("connect") { (nodeId: String) -> Bool in
            return try await connectToNode(nodeId: nodeId)
        }
        
        AsyncFunction("disconnect") { (nodeId: String) -> Bool in
            return try await disconnectFromNode(nodeId: nodeId)
        }
        
        AsyncFunction("listConnections") { () -> [String] in
            return try await listConnections()
        }
        
        // MARK: - Sharing Operations
        AsyncFunction("shareDocument") { (documentId: String) -> String in
            return try await shareDocument(documentId: documentId)
        }
        
        AsyncFunction("receiveDocument") { (ticket: String, promise: Promise) in
            Task {
                do {
                    let document = try await receiveDocument(ticket: ticket) { progress in
                        // Send progress updates
                        self.sendEvent("transferProgress", [
                            "transferred": progress.transferred,
                            "total": progress.total,
                            "percentage": progress.percentage,
                            "status": progress.status
                        ])
                    }
                    promise.resolve(document)
                } catch {
                    promise.reject("RECEIVE_ERROR", error.localizedDescription)
                }
            }
        }
        
        // MARK: - Events
        Events("transferProgress")
    }
}

// MARK: - Private Implementation

private var irohNode: IrohNodeWrapper?

private func createIrohNode(config: [String: Any]?) async throws -> [String: Any] {
    // In a real implementation, this would use the actual iroh-ffi Swift bindings
    // For now, we'll create a wrapper that simulates the functionality
    
    let nodeId = config?["nodeId"] as? String ?? generateNodeId()
    irohNode = IrohNodeWrapper(nodeId: nodeId)
    
    try await irohNode?.initialize()
    
    return [
        "id": nodeId,
        "isConnected": true,
        "documents": []
    ]
}

private func getNodeInfo() async throws -> [String: Any] {
    guard let node = irohNode else {
        throw IrohError.nodeNotInitialized
    }
    
    return [
        "id": node.nodeId,
        "isConnected": node.isConnected,
        "documents": try await node.listDocuments()
    ]
}

private func shutdownNode() async throws -> Bool {
    guard let node = irohNode else {
        return false
    }
    
    try await node.shutdown()
    irohNode = nil
    return true
}

private func createDocument(name: String) async throws -> [String: Any] {
    guard let node = irohNode else {
        throw IrohError.nodeNotInitialized
    }
    
    return try await node.createDocument(name: name)
}

private func getDocument(documentId: String) async throws -> [String: Any]? {
    guard let node = irohNode else {
        throw IrohError.nodeNotInitialized
    }
    
    return try await node.getDocument(documentId: documentId)
}

private func listDocuments() async throws -> [[String: Any]] {
    guard let node = irohNode else {
        throw IrohError.nodeNotInitialized
    }
    
    return try await node.listDocuments()
}

private func deleteDocument(documentId: String) async throws -> Bool {
    guard let node = irohNode else {
        throw IrohError.nodeNotInitialized
    }
    
    return try await node.deleteDocument(documentId: documentId)
}

private func addBlob(data: Data) async throws -> [String: Any] {
    guard let node = irohNode else {
        throw IrohError.nodeNotInitialized
    }
    
    return try await node.addBlob(data: data)
}

private func getBlob(hash: String) async throws -> Data? {
    guard let node = irohNode else {
        throw IrohError.nodeNotInitialized
    }
    
    return try await node.getBlob(hash: hash)
}

private func addFile(filePath: String) async throws -> [String: Any] {
    guard let node = irohNode else {
        throw IrohError.nodeNotInitialized
    }
    
    let fileURL = URL(fileURLWithPath: filePath)
    let data = try Data(contentsOf: fileURL)
    return try await node.addBlob(data: data)
}

private func exportFile(hash: String, outputPath: String) async throws -> Bool {
    guard let node = irohNode else {
        throw IrohError.nodeNotInitialized
    }
    
    guard let data = try await node.getBlob(hash: hash) else {
        return false
    }
    
    let outputURL = URL(fileURLWithPath: outputPath)
    try data.write(to: outputURL)
    return true
}

private func connectToNode(nodeId: String) async throws -> Bool {
    guard let node = irohNode else {
        throw IrohError.nodeNotInitialized
    }
    
    return try await node.connect(to: nodeId)
}

private func disconnectFromNode(nodeId: String) async throws -> Bool {
    guard let node = irohNode else {
        throw IrohError.nodeNotInitialized
    }
    
    return try await node.disconnect(from: nodeId)
}

private func listConnections() async throws -> [String] {
    guard let node = irohNode else {
        throw IrohError.nodeNotInitialized
    }
    
    return try await node.listConnections()
}

private func shareDocument(documentId: String) async throws -> String {
    guard let node = irohNode else {
        throw IrohError.nodeNotInitialized
    }
    
    return try await node.shareDocument(documentId: documentId)
}

private func receiveDocument(ticket: String, onProgress: @escaping (TransferProgress) -> Void) async throws -> [String: Any] {
    guard let node = irohNode else {
        throw IrohError.nodeNotInitialized
    }
    
    return try await node.receiveDocument(ticket: ticket, onProgress: onProgress)
}

// MARK: - Helper Functions

private func generateNodeId() -> String {
    return "node_\(Int(Date().timeIntervalSince1970))_\(UUID().uuidString.prefix(8))"
}

// MARK: - Error Types

enum IrohError: Error, LocalizedError {
    case nodeNotInitialized
    case documentNotFound
    case connectionFailed
    case transferFailed
    
    var errorDescription: String? {
        switch self {
        case .nodeNotInitialized:
            return "Iroh node not initialized"
        case .documentNotFound:
            return "Document not found"
        case .connectionFailed:
            return "Failed to connect to peer"
        case .transferFailed:
            return "File transfer failed"
        }
    }
}

// MARK: - Data Types

struct TransferProgress {
    let transferred: Int
    let total: Int
    let percentage: Double
    let status: String
}

// MARK: - Node Wrapper Class

private class IrohNodeWrapper {
    let nodeId: String
    var isConnected: Bool = false
    private var documents: [String: [String: Any]] = [:]
    private var blobs: [String: Data] = [:]
    private var connections: Set<String> = []
    
    init(nodeId: String) {
        self.nodeId = nodeId
    }
    
    func initialize() async throws {
        // In a real implementation, this would initialize the actual iroh node
        // using the iroh-ffi Swift bindings
        isConnected = true
        
        // Load persisted data
        await loadPersistedData()
    }
    
    func shutdown() async throws {
        // Persist data before shutdown
        await persistData()
        isConnected = false
        connections.removeAll()
    }
    
    func createDocument(name: String) async throws -> [String: Any] {
        let documentId = "doc_\(Int(Date().timeIntervalSince1970))_\(UUID().uuidString.prefix(8))"
        let document: [String: Any] = [
            "id": documentId,
            "name": name,
            "size": 0,
            "created": ISO8601DateFormatter().string(from: Date()),
            "modified": ISO8601DateFormatter().string(from: Date())
        ]
        
        documents[documentId] = document
        await persistData()
        
        return document
    }
    
    func getDocument(documentId: String) async throws -> [String: Any]? {
        return documents[documentId]
    }
    
    func listDocuments() async throws -> [[String: Any]] {
        return Array(documents.values)
    }
    
    func deleteDocument(documentId: String) async throws -> Bool {
        let removed = documents.removeValue(forKey: documentId) != nil
        if removed {
            await persistData()
        }
        return removed
    }
    
    func addBlob(data: Data) async throws -> [String: Any] {
        let hash = calculateHash(data: data)
        blobs[hash] = data
        
        await persistData()
        
        return [
            "hash": hash,
            "size": data.count,
            "format": "raw"
        ]
    }
    
    func getBlob(hash: String) async throws -> Data? {
        return blobs[hash]
    }
    
    func connect(to nodeId: String) async throws -> Bool {
        // In a real implementation, this would establish a P2P connection
        connections.insert(nodeId)
        return true
    }
    
    func disconnect(from nodeId: String) async throws -> Bool {
        return connections.remove(nodeId) != nil
    }
    
    func listConnections() async throws -> [String] {
        return Array(connections)
    }
    
    func shareDocument(documentId: String) async throws -> String {
        guard documents[documentId] != nil else {
            throw IrohError.documentNotFound
        }
        
        // Generate a share ticket
        let ticket = [
            "sourceNode": nodeId,
            "documentId": documentId,
            "timestamp": Int(Date().timeIntervalSince1970)
        ]
        
        let ticketData = try JSONSerialization.data(withJSONObject: ticket)
        return ticketData.base64EncodedString()
    }
    
    func receiveDocument(ticket: String, onProgress: @escaping (TransferProgress) -> Void) async throws -> [String: Any] {
        // Parse ticket
        guard let ticketData = Data(base64Encoded: ticket),
              let ticketInfo = try JSONSerialization.jsonObject(with: ticketData) as? [String: Any],
              let sourceNode = ticketInfo["sourceNode"] as? String else {
            throw IrohError.transferFailed
        }
        
        // Simulate transfer progress
        for i in stride(from: 0, through: 100, by: 10) {
            let progress = TransferProgress(
                transferred: i,
                total: 100,
                percentage: Double(i),
                status: i == 100 ? "completed" : "transferring"
            )
            onProgress(progress)
            
            // Small delay to simulate transfer
            try await Task.sleep(nanoseconds: 100_000_000) // 0.1 seconds
        }
        
        // Create received document
        let document = try await createDocument(name: "Received from \(sourceNode)")
        return document
    }
    
    private func calculateHash(data: Data) -> String {
        // Simple hash implementation for demo
        var hash = data.reduce(0) { result, byte in
            return (result &* 31 &+ Int(byte)) & 0x7FFFFFFF
        }
        return "hash_\(String(hash, radix: 16))_\(data.count)"
    }
    
    private func loadPersistedData() async {
        // In a real implementation, load from UserDefaults or Core Data
        if let documentsData = UserDefaults.standard.data(forKey: "iroh_documents_\(nodeId)") {
            do {
                if let loadedDocs = try JSONSerialization.jsonObject(with: documentsData) as? [String: [String: Any]] {
                    documents = loadedDocs
                }
            } catch {
                print("Failed to load documents: \(error)")
            }
        }
    }
    
    private func persistData() async {
        // In a real implementation, save to UserDefaults or Core Data
        do {
            let documentsData = try JSONSerialization.data(withJSONObject: documents)
            UserDefaults.standard.set(documentsData, forKey: "iroh_documents_\(nodeId)")
        } catch {
            print("Failed to persist documents: \(error)")
        }
    }
}